<!DOCTYPE html>
<html lang="zh-CN">
<head hexo-theme='https://github.com/volantis-x/hexo-theme-volantis/tree/4.1.5'>
  <meta charset="utf-8">
  <!-- SEO相关 -->
  
    
  
  <!-- 渲染优化 -->
  <meta http-equiv='x-dns-prefetch-control' content='on' />
  <link rel='dns-prefetch' href='https://cdn.jsdelivr.net'>
  <link rel="preconnect" href="https://cdn.jsdelivr.net" crossorigin>
  <meta name="renderer" content="webkit">
  <meta name="force-rendering" content="webkit">
  <meta http-equiv="X-UA-Compatible" content="IE=Edge,chrome=1">
  <meta name="HandheldFriendly" content="True" >
  <meta name="apple-mobile-web-app-capable" content="yes">
  <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">

  <!-- 页面元数据 -->
  
  <title>Substrate入门 - Schenk - Blog</title>
  
    <meta name="keywords" content="blockchain">
  

  
    <meta name="description" content="Substrate框架入门">
  

  <!-- feed -->
  

  <!-- import meta -->
  

  <!-- link -->
  
    <link rel="shortcut icon" type='image/x-icon' href="https://cdn.jsdelivr.net/gh/Schenk75/Source/logos/steroids.svg">
  

  <!-- import link -->
  

  
    
<link rel="stylesheet" href="/css/first.css">

  

  
  <link rel="stylesheet" href="/css/style.css" media="print" onload="this.media='all';this.onload=null">
  <noscript><link rel="stylesheet" href="/css/style.css"></noscript>
  

  <script id="loadcss"></script>

</head>

<body>
  

<header id="l_header" class="l_header auto shadow blur floatable show" style='opacity: 0' >
  <div class='container'>
  <div id='wrapper'>
    <div class='nav-sub'>
      <p class="title"></p>
      <ul class='switcher nav-list-h m-phone' id="pjax-header-nav-list">
        <li><a id="s-comment" class="fas fa-comments fa-fw" target="_self" href='javascript:void(0)'></a></li>
        
          <li><a id="s-toc" class="s-toc fas fa-list fa-fw" target="_self" href='javascript:void(0)'></a></li>
        
      </ul>
    </div>
		<div class="nav-main">
      
        
        <a class="title flat-box" target="_self" href='/'>
          
            <img no-lazy class='logo' src='https://cdn.jsdelivr.net/gh/Schenk75/Source@latest/logos/taiga.svg'/>
          
          
          
        </a>
      

			<div class='menu navigation'>
				<ul class='nav-list-h m-pc'>
          
          
          
            
            
              <li>
                <a class="menuitem flat-box faa-parent animated-hover" href=/
                  
                  
                  
                    id="home"
                  >
                  <i class='fab fa-stack-overflow fa-fw'></i>主页
                </a>
                
              </li>
            
          
          
            
            
              <li>
                <a class="menuitem flat-box faa-parent animated-hover" href=/categories/
                  
                  
                  
                    id="categories"
                  >
                  <i class='fas fa-folder-open fa-fw'></i>分类
                </a>
                
              </li>
            
          
          
            
            
              <li>
                <a class="menuitem flat-box faa-parent animated-hover" href=/tags/
                  
                  
                  
                    id="tags"
                  >
                  <i class='fas fa-tags fa-fw'></i>标签
                </a>
                
              </li>
            
          
          
            
            
              <li>
                <a class="menuitem flat-box faa-parent animated-hover" href=/archives/
                  
                  
                  
                    id="archives"
                  >
                  <i class='fas fa-archive fa-fw'></i>归档
                </a>
                
              </li>
            
          
          
            
            
              <li>
                <a class="menuitem flat-box faa-parent animated-hover" href=/friends/
                  
                  
                  
                    id="friends"
                  >
                  <i class='fas fa-link fa-fw'></i>友链
                </a>
                
              </li>
            
          
          
            
            
              <li>
                <a class="menuitem flat-box faa-parent animated-hover" href=/about/
                  
                  
                  
                    id="about"
                  >
                  <i class='fas fa-info-circle fa-fw'></i>关于
                </a>
                
              </li>
            
          
          
				</ul>
			</div>

      <div class="m_search">
        <form name="searchform" class="form u-search-form">
          <i class="icon fas fa-search fa-fw"></i>
          <input type="text" class="input u-search-input" placeholder="Search..." />
        </form>
      </div>

			<ul class='switcher nav-list-h m-phone'>
				
					<li><a class="s-search fas fa-search fa-fw" target="_self" href='javascript:void(0)'></a></li>
				
				<li>
          <a class="s-menu fas fa-bars fa-fw" target="_self" href='javascript:void(0)'></a>
          <ul class="menu-phone list-v navigation white-box">
            
              
            
              <li>
                <a class="menuitem flat-box faa-parent animated-hover" href=/
                  
                  
                  
                    id="home"
                  >
                  <i class='fab fa-stack-overflow fa-fw'></i>主页
                </a>
                
              </li>
            
          
            
              
            
              <li>
                <a class="menuitem flat-box faa-parent animated-hover" href=/categories/
                  
                  
                  
                    id="categories"
                  >
                  <i class='fas fa-folder-open fa-fw'></i>分类
                </a>
                
              </li>
            
          
            
              
            
              <li>
                <a class="menuitem flat-box faa-parent animated-hover" href=/tags/
                  
                  
                  
                    id="tags"
                  >
                  <i class='fas fa-tags fa-fw'></i>标签
                </a>
                
              </li>
            
          
            
              
            
              <li>
                <a class="menuitem flat-box faa-parent animated-hover" href=/archives/
                  
                  
                  
                    id="archives"
                  >
                  <i class='fas fa-archive fa-fw'></i>归档
                </a>
                
              </li>
            
          
            
              
            
              <li>
                <a class="menuitem flat-box faa-parent animated-hover" href=/friends/
                  
                  
                  
                    id="friends"
                  >
                  <i class='fas fa-link fa-fw'></i>友链
                </a>
                
              </li>
            
          
            
              
            
              <li>
                <a class="menuitem flat-box faa-parent animated-hover" href=/about/
                  
                  
                  
                    id="about"
                  >
                  <i class='fas fa-info-circle fa-fw'></i>关于
                </a>
                
              </li>
            
          
            
          </ul>
        </li>
			</ul>
		</div>
	</div>
  </div>
</header>

  <div id="l_body">
    <div id="l_cover">
  
    
        <div id="full" class='cover-wrapper post dock' style="display: none;">
          
            <div class='cover-bg lazyload placeholder' data-bg="https://cdn.jsdelivr.net/gh/Schenk75/Source@master/common/interstellar.jpg"></div>
          
          <div class='cover-body'>
  <div class='top'>
    
    
      <p class="title">Schenk - Blog</p>
    
    
      <p class="subtitle">SJTUer | Cuber</p>
    
  </div>
  <div class='bottom'>
    <div class='menu navigation'>
      <div class='list-h'>
        
          
            <a href="/categories/"
              
              
              id="categories">
              <i class='fas fa-folder-open fa-fw'></i><p>分类</p>
            </a>
          
            <a href="/tags/"
              
              
              id="tags">
              <i class='fas fa-tags fa-fw'></i><p>标签</p>
            </a>
          
            <a href="/archives/"
              
              
              id="archives">
              <i class='fas fa-archive fa-fw'></i><p>归档</p>
            </a>
          
            <a href="/friends/"
              
              
              id="friends">
              <i class='fas fa-link fa-fw'></i><p>友链</p>
            </a>
          
            <a href="/about/"
              
              
              id="about">
              <i class='fas fa-info-circle fa-fw'></i><p>关于</p>
            </a>
          
        
      </div>
    </div>
  </div>
</div>

          <div id="scroll-down" style="display: none;"><i class="fa fa-chevron-down scroll-down-effects"></i></div>
        </div>
    
  
  </div>

    <div id='safearea'>
      <div class='body-wrapper' id="pjax-container">
        

<div class='l_main'>
  <article class="article post white-box reveal md shadow floatable article-type-post" id="post" itemscope itemprop="blogPost">
  


  
  <div class="article-meta" id="top">
    
    
    
      <h1 class="title">
        Substrate入门
      </h1>
      <div class='new-meta-box'>
        
          
            
<div class='new-meta-item author'>
  <a class='author' href="/" rel="nofollow">
    <img no-lazy src="https://cdn.jsdelivr.net/gh/Schenk75/Source@master/common/avatar.jpg">
    <p>Schenk</p>
  </a>
</div>

          
        
          
            
  <div class='new-meta-item category'>
    <a class='notlink'>
      <i class="fas fa-folder-open fa-fw" aria-hidden="true"></i>
      <a class="category-link" href="/categories/Notes/">Notes</a>
    </a>
  </div>


          
        
          
            <div class="new-meta-item date" itemprop="dateUpdated" datetime="2022-04-28T15:47:50+08:00">
  <a class='notlink'>
    <i class="fas fa-edit fa-fw" aria-hidden="true"></i>
    <p>更新于：2022年4月28日</p>
  </a>
</div>

          
        
          
            
  <div class="new-meta-item wordcount">
    <a class='notlink'>
      <i class="fas fa-keyboard fa-fw" aria-hidden="true"></i>
      <p>字数：7k字</p>
    </a>
  </div>
  <div class="new-meta-item readtime">
    <a class='notlink'>
      <i class="fas fa-hourglass-half fa-fw" aria-hidden="true"></i>
      <p>时长：24分钟</p>
    </a>
  </div>


          
        
          
            
  <div class="new-meta-item browse leancloud">
    <a class='notlink'>
      
      <div id="lc-pv" data-title="Substrate入门" data-path="/2021/04/15/learning-notes/substrate%E5%85%A5%E9%97%A8/">
        <i class="fas fa-eye fa-fw" aria-hidden="true"></i>
        <span id='number'><i class="fas fa-circle-notch fa-spin fa-fw" aria-hidden="true"></i></span>
        次浏览
      </div>
    </a>
  </div>


          
        
      </div>
    
  </div>


  
  <h2 id="Start">Start</h2>
<h3 id="架构">架构</h3>
<p><img src="https://cdn.jsdelivr.net/gh/Schenk75/Source@master/notes/substrate%E5%85%A5%E9%97%A8/image-20210415151111761.png" class="lazyload" data-srcset="https://cdn.jsdelivr.net/gh/Schenk75/Source@master/notes/substrate%E5%85%A5%E9%97%A8/image-20210415151111761.png" srcset="" alt="image-20210415151111761"></p>
<p>substrate客户端的基本构成：</p>
<ul>
<li>存储(storage)：存储区块链的状态，使用简单而高效的键值对存储机制</li>
<li>运行时(Runtime)：定义处理块的逻辑，包括状态转换逻辑。运行时代码的编译方式有两种：
<ul>
<li>编译为wasm并且存储在区块链上（使得forkless运行时升级成为可能）</li>
<li>使用本机运行时编译为客户端的本机代码</li>
</ul>
</li>
<li>点对点网络(peer-to-peer network)：使用rust的 <code>libp2p</code> 实现</li>
<li>共识(consensus)：substrate可以定制共识引擎，也提供一些建立在Web3 Foundation研究之上的共识机制</li>
<li>RPC(remote procedure call)：使区块链用户可以与网络交互，substrate提供HTTP和WebSocket RPC服务器</li>
<li>Telemetry：由嵌入式Prometheus服务器公开的客户端度量</li>
</ul>
<h2 id="基本概念">基本概念</h2>
<h3 id="Extrinsics">Extrinsics</h3>
<p>extrinsic指来自链外的信息，并且会被纳入区块中。分为三类：</p>
<ul>
<li>inherents</li>
<li>签名交易</li>
<li>无签名交易</li>
</ul>
<p><strong>注：</strong> 执行函数时触发的事件不属于extrinsic</p>
<h4 id="区块结构">区块结构</h4>
<p>substrate的每一个区块都是由一个区块头和一组extrinsic组成的，区块头包含区块高度、父区块哈希值、extrinsic根哈希值、链上状态根哈希值，以及摘要等信息。</p>
<p>Extrinsics 被打包到当前区块中并会被执行，每个extrinsic都在runtime进行了定义。  Extrinsic根哈希值主要有两个用途：</p>
<ul>
<li>它可以在区块头构建和分发完成之后，防止任何人对该区块头中所含extrinsic内容进行篡改</li>
<li>它提供了一种方法，在只有区块头信息的条件下，可以帮助轻客户端快速地验证某一区块中存在某笔交易</li>
</ul>
<h4 id="Inherents">Inherents</h4>
<p>Inherent指的是那些仅能由区块创建者插入到区块当中的无签名信息，他们不会在网络上传播或存储在交易队列中</p>
<p>只要足够多的区块验证人认可该Inherent的合理性，那么这条Inherent就是有效的</p>
<h4 id="签名交易">签名交易</h4>
<p>签名交易包含了签发该交易的账户私钥签名，这意味着此账户同意承担相应的区块打包费用</p>
<p>由于签名交易打包上链的费用在交易执行前就可以被识别，所以在网络节点中传播此类交易造成信息泛滥的风险很小</p>
<h4 id="无签名交易">无签名交易</h4>
<p>无签名交易意味着无人支付交易费用，这使得交易队列无法用有效的手段来防止其被滥用</p>
<p>无签名交易里缺失nonce字段来辅助识别交易执行顺序，从而难以防止重放攻击</p>
<p>少数交易能够安全使用不具签名的形式，前提是它们需要提供 <code>SignedExtension</code> 的自定义实现，来防止垃圾交易</p>
<h4 id="Signed-Extension">Signed Extension</h4>
<p><code>SignedExtension</code> 是一个trait ，通过它可以使用额外的数据或逻辑来扩展交易</p>
<p>在交易执行之前，任何时候需要获取某笔特定交易信息时，都可以使用 <code>SignedExtension</code> 来实现。 因此<code>SignedExtension</code> 在交易队列中被大量使用</p>
<p>Runtime会使用 <code>SignedExtension</code> 提供的一些数据，比如用来计算可调用函数<code>Call</code>的交易费用</p>
<p><code>SignedExtension</code> 还包含一个名为<code>AdditionalSigned</code>的字段，这个字段可存放任意可编码数据，因而能够在打包或者发送交易之前，被用来执行自定义逻辑</p>
<p>为了避免将可能失败的交易打包进区块中，交易队列还会定期调用 <code>SignedExtension</code> 的函数来验证即将进入区块的交易</p>
<p><code>SignedExtension</code> 也可以用于验证无签名交易：通过实现<code>*_unsigned</code> 的一系列方法，来封装信息核验、防垃圾信息和重放保护等逻辑，供交易池使用</p>
<h3 id="交易池">交易池</h3>
<p>交易池包含所有在网络广播的，已被本地节点接收和验证的交易（签名和未签名的）</p>
<blockquote>
<p>有效性</p>
</blockquote>
<p>交易池检查交易是否有效（由Runtime决定）：</p>
<ul>
<li>检查交易索引 (nonce) 是否正确</li>
<li>检查帐户是否有足够的资金来支付相关费用</li>
<li>检查签名是否有效</li>
</ul>
<p>交易池还定期检查池内现有交易的有效性： 如果发现无效或过期的普通交易，该交易将被交易池删除</p>
<blockquote>
<p>排序</p>
</blockquote>
<p>如果交易是有效的，交易队列会将交易分为两组：</p>
<ul>
<li>就绪队列：包含所有可放到新的待处理区块中的交易。 对于随 <code>FRAME</code> 构建的 Runtime，所有交易必须严格遵循就绪队列中的顺序</li>
<li>未来队列：包含所有可能在未来变成有效的交易。例如，一个交易可能有一个对其账户来说过高的 nonce 值，此交易将在未来队列中等待，直到之前的交易上传至区块链上</li>
</ul>
<h4 id="交易依赖关系">交易依赖关系</h4>
<p><code>ValidTransaction</code> 结构体定义了 <code>requires</code> 、 <code>provides</code> 和 <code>priority</code> 参数来构建交易的依赖关系。 这个依赖关系允许交易池产生有效线性顺序的交易</p>
<p>对于用 <code>FRAME</code> 构建的 Runtime，节点基于不同账户对交易进行排序。 所有签名交易都需要包含一个交易索引 (nonce)，该索引值在每次进行新的交易时都会递增1</p>
<p><code>FRAME</code> 交易包括一个 <code>provides</code> 标签（值为 <code>encode(sender ++ nonce)</code> ），和 <code>requires</code> 标签（值为 <code>encode(sender ++ (nonce -1)) if nonce &gt; 1</code> ）。来自单一发送人的所有交易将形成一个序列。</p>
<h4 id="交易优先级">交易优先级</h4>
<p><code>ValidTransaction</code> 结构体中的 <code>priority</code> 决定了就绪队列中的交易顺序，<code>priority</code> 定义了当一个交易可解锁多个依赖交易时，所应有的线性排序</p>
<p>当某个节点成为下一个区块生成者时，它将在下一个区块把交易按优先级别从高到低排序，直到达到区块的长度限制</p>
<p>对于用 <code>FRAME</code> 构建的 Runtime，<code>priority</code> 定义为交易要支付的 <code>fee</code> (费用)。 例如：</p>
<ul>
<li>如果我们从不同的发送者那里收到 2 个交易（而且 <code>nonce=0</code> 时），我们通过 <code>priority</code> 来确定哪个交易更为重要，并优先把它打包进区块中</li>
<li>如果我们从同一个发送方收到 2 个相同 <code>nonce</code> 的交易，那么只会有一个交易会被打包到链上。 我们使用 <code>priority</code> 来选择 <code>fee</code> 较高的交易，并把它储存到交易池中。</li>
</ul>
<blockquote>
<p>注意：交易池并不知道费用、账户、或签名，它只处理交易的有效性和 <code>priority</code>、<code>requires</code> 和 <code>provides</code> 参数这些抽象概念。 所有其他详细信息都是由 Runtime 通过 <code>validate_transaction</code> 函数定义的。</p>
</blockquote>
<h4 id="交易的流程">交易的流程</h4>
<p>交易可以遵循两条路径：</p>
<h5 id="我们的节点生成的区块">我们的节点生成的区块</h5>
<ol>
<li>我们的节点会监听网络上的交易</li>
<li>每一笔交易都要经过验证，而有效的交易会被放入交易池</li>
<li>交易池负责对交易进行排序，并返回可被纳入区块的交易。在就绪队列中的交易将被用来打包到区块内</li>
<li>交易会被执行，而状态变化会存在本地内存中。来自就绪队列的交易也会在网络上传播给其他节点</li>
<li>构建好的区块会被发布到网络上，而网络上其他所有节点都会接收并执行该区块</li>
</ol>
<blockquote>
<p>注意：交易在区块生成时不会从就绪队列中被删除，只有在区块导入时才被删除，这是因为最新生成的区块有可能进不了规范链里</p>
</blockquote>
<h5 id="从网络接收的区块">从网络接收的区块</h5>
<p>该区块被执行后，整个区块要么成功，要么失败</p>
<h4 id="交易有效性">交易有效性</h4>
<p><code>validate_transaction</code> 是在 Runtime 里被调用的，检查有效的签名和 nonce 并返回一个 <code>Result</code></p>
<p><code>validate_transaction</code> 只会个别地检查交易，所以它不会捡测到类似同一输出被使用两次的错误。</p>
<p><code>validate_transaction</code> 并不会检查对模块的调用是否成功</p>
<p><code>validate_transaction</code> 函数应专注于为交易池提供必要的信息，以便对交易进行排序和优先处理，并快速拒绝所有无效或过时的交易</p>
<h3 id="账户">账户</h3>
<p>substrate用公私钥对来表示网络参与者</p>
<h4 id="账户密钥">账户密钥</h4>
<ul>
<li>
<p>密钥对代表一个账户，并可以控制资金</p>
</li>
<li>
<p>账户密钥是通过泛型定义的，在 Runtime 中进行实例化</p>
</li>
</ul>
<h4 id="Stash密钥">Stash密钥</h4>
<p>Stash 账户的公私钥对，这个账户就像一个 “储蓄账户”，不应该用它进行频繁的交易。因此，应以最安全的方式来保存其私钥</p>
<h4 id="Controller密钥">Controller密钥</h4>
<p>Controller 帐户的公私钥对，在 Substrate 的 NPOS 模型中，Controller 密钥会代表某个账户进行验证或提名</p>
<p>Controller 账户只需要支付交易费用，所以它只需要最少量的资金</p>
<h3 id="会话密钥">会话密钥</h3>
<ul>
<li>会话密钥是验证者用来签署和共识相关消息的 “热密钥”</li>
<li>更改会话密钥的方式：通过Controller账户对会话公钥签名并创建一个证书，再将证书通过 extrinsic 广播</li>
<li>会话密钥是通过泛型定义的，在 Runtime 中进行实例化</li>
</ul>
<h3 id="交易权重">交易权重</h3>
<ul>
<li>链可用的资源是有限的，包括内存、存储 I/O、算力、交易/区块大小和状态数据库的大小</li>
<li>权重用于管理<strong>验证一个区块所用的时间</strong>，常用于限制存储I/O和算力</li>
<li>区块中可包含的权重总量是有限的，并且可用的权重消耗通常也会受到交易费的限制</li>
<li>最大区块权重应等于目标区块时间的三分之一，为区块构造分配、网络传播、导入和验证各分配三分之一</li>
</ul>
<h4 id="权重基础">权重基础</h4>
<p>对权重的计算应该满足：</p>
<ul>
<li>在被调用之前可计算。区块创建者在实际决定是否接受某个交易之前应该能够检查其权重</li>
<li>本身消耗很少的资源。 如果计算交易的权重会消耗与执行交易消耗相似的资源，那这样就没有意义了。因此，权重计算应该比执行交易更轻量级</li>
<li>无需访问链上状态即可确定使用的资源。权重有利于表示固定的度量或仅基于少量 I/O 的可调用函数参数的测量</li>
</ul>
<p>如果权重十分依赖于链上状态，则可以：</p>
<ul>
<li>强制使用可调用函数可能消耗的权重上限。 如果可调用函数使用的强制权重上限与其下限差别只是很少，则可直接使用其权重上限而无需访问链上状态。 但是如果两者差别巨大，那么即使进行很少的交易，其经济成本也可能很大，这将破坏激励措施，并降低链上吞吐量</li>
<li>要求有效权重作为参数传递到可调用函数中。 消耗的权量应基于这些参数，同时也应包含在调用时验证它们所花费的时间。 必须经过这一验证过程以确保权重参数准确对应于链上状态，如果对应不上，则报错</li>
</ul>
<h3 id="链下功能">链下功能</h3>
<p>使用预言机(Oracle)先对链下的数据作查询或处理，然后才将其提交到链上</p>
<p>预言机是一种外部服务，通常用于监听区块链事件，并根据条件触发任务。 当这些任务执行完毕，执行结果会以交易的形式提交至区块链上。 虽然这种方法可行，但在安全性、可扩展性，和效率方面仍然存在一些缺陷</p>
<p>因此substrate提供一些链下特性：</p>
<ul>
<li>Off-Chain Worker (OCW) 执行长时间运行的和可能不确定的任务（如web请求、加解密、数据签名、随机数生成、cpu密集型计算、链上数据枚举/聚合等）</li>
<li>Off-Chain Storage 为substrate节点提供本地存储</li>
<li>Off-Chain Indexing 允许Runtime独立于OCW直接写入链下存储</li>
</ul>
<p><img src="https://cdn.jsdelivr.net/gh/Schenk75/Source@master/notes/substrate%E5%85%A5%E9%97%A8/image-20210415193130034.png" class="lazyload" data-srcset="https://cdn.jsdelivr.net/gh/Schenk75/Source@master/notes/substrate%E5%85%A5%E9%97%A8/image-20210415193130034.png" srcset="" alt="image-20210415193130034"></p>
<h2 id="Runtime">Runtime</h2>
<ul>
<li>
<p>Runtime 用于定义区块链的业务逻辑</p>
</li>
<li>
<p>在 Runtime 中定义了用于表示区块链状态的存储项，同时也定义了允许区块链用户对该状态进行更改的函数</p>
</li>
<li>
<p>为了能够提供无须分叉的升级功能，Substrate采用了可编译成 WebAssembly (Wasm)字节码的 Runtime 形式</p>
</li>
<li>
<p>FRAME 是Parity 的 Substrate runtime 开发系统， FRAME 定义了额外的 runtime 基础类型，并提供了一个框架，使得通过编写模块 (称为 “pallets”) 来构建 runtime 变得十分容易，每个 pallet 用于封装特定于该域的逻辑，这些逻辑可表示为一组存储项、事件、错误和可调用函数的集合</p>
</li>
</ul>
<h3 id="Runtime基本类型">Runtime基本类型</h3>
<h4 id="核心原语">核心原语</h4>
<p>runtime必须提供给substrate其他层的最小化内容：</p>
<ul>
<li><code>Hash</code>：数据摘要， 通常是一个256位的数值</li>
<li><code>DigestItem</code></li>
<li><code>Digest</code>：一系列 <code>DigestItem</code> 的组合， 它对当前区块中轻客户端所需知晓的所有信息进行了编码</li>
<li><code>Extrinsic</code>：这种类型代表着一段来自链外、且被区块链认可的数据。 它通常包括一个或多个签名，以及某种编码指令(例如转移资金所有权或调用智能合约)</li>
<li><code>Header</code>：包含了单个区块所有信息 (以加密或其它形式) 的类型。 它包括父区块哈希、存储根哈希和 extrinsic 根哈希、区块摘要及区块号</li>
<li><code>Block</code>：基本上就是 <code>Header</code> 和一系列 <code>Extrinsics</code> 的组合，以及所使用的哈希算法说明</li>
<li><code>BlockNumber</code>：一种类型，代表一个有效区块的祖先区块的总数量。 通常是32字节数值</li>
</ul>
<h4 id="FRAME原语">FRAME原语</h4>
<p>如果是通过Substrate FRAME搭建的runtime，还可以使用如下的FRAME原语：</p>
<ul>
<li><code>Call</code>: 通过extrinsic调用的可调用函数类型</li>
<li><code>Origin</code>: 代表着函数调用方，例如可以是签名消息(交易)、无签名消息(区块链内生数据)，或者runtime本身(根调用)</li>
<li><code>Index</code>: 帐户的交易索引 (nonce) 类型， 存储交易发送方账户曾经发出的交易总数</li>
<li><code>Hashing</code>：在runtime中使用的哈希系统 (算法)</li>
<li><code>AccountId</code>： 用于在runtime中识别用户账户的类型</li>
<li><code>Event</code>：代表runtime发出的事件类型</li>
<li><code>Version</code>：代表runtime版本的类型</li>
</ul>
<h3 id="FRAME">FRAME</h3>
<p><strong>Framework for Runtime Aggregation of Modularized Entities (FRAME)</strong> 是一组可简化 runtime 开发的模块（pallet）和支持库</p>
<p>FRAME 提供了一些与 Substrate Primitives 交互的帮助模块，而 Substrate Primitives 则提供了与核心客户端的交互接口</p>
<p><img src="https://cdn.jsdelivr.net/gh/Schenk75/Source@master/notes/substrate%E5%85%A5%E9%97%A8/image-20210415195250082.png" class="lazyload" data-srcset="https://cdn.jsdelivr.net/gh/Schenk75/Source@master/notes/substrate%E5%85%A5%E9%97%A8/image-20210415195250082.png" srcset="" alt="image-20210415195250082"></p>
<h3 id="Pallet">Pallet</h3>
<p>Pallets是一种可组合成为Substrate runtime的特殊Rust模块。每个pallet都拥有独立的逻辑，可修改相应区块链状态转换函数的特征和功能</p>
<h4 id="Pallet架构">Pallet架构</h4>
<figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 1. 导入库和依赖项</span></span><br><span class="line"><span class="comment">// 此pallet支持使用任何带有`no_std`标志编译的Rust库。</span></span><br><span class="line"><span class="keyword">use</span> support::&#123;decl_module, decl_event, decl_storage, ...&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 2. Runtime配置Trait </span></span><br><span class="line"><span class="comment">// 所有runtime类型和常量都放在这里。</span></span><br><span class="line"><span class="comment">// 如果此pallet依赖于其他特定的pallet，则应将依赖pallet的配置trait添加到继承的trait列表中</span></span><br><span class="line"><span class="keyword">pub</span> <span class="class"><span class="keyword">trait</span> <span class="title">Config</span></span>: system::Config &#123; ... &#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 3. Runtime事件</span></span><br><span class="line"><span class="comment">// 事件是一种用于报告特定条件和情况发生的简单手段，用户、Dapp和区块链浏览器都可能对事件的感兴趣。没有它就很难发现。</span></span><br><span class="line">decl_event!&#123; ... &#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 4. Runtime存储</span></span><br><span class="line"><span class="comment">// Runtime存储允许在保证“类型安全“前提下使用Substrate存储数据库，因而可在块与块之间留存内容。</span></span><br><span class="line">decl_storage!&#123; ... &#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 5. Pallet声明 </span></span><br><span class="line"><span class="comment">// 此模块定义了最终从此pallet导出的&quot;Module&quot;结构体</span></span><br><span class="line"><span class="comment">// 它定义了该pallet公开的可调用函数，并在区块执行时协调该pallet行为</span></span><br><span class="line">decl_module! &#123; ... &#125;</span><br></pre></td></tr></table></figure>
<h4 id="Substrate内置模块">Substrate内置模块</h4>
<p><a target="_blank" rel="noopener" href="https://substrate.dev/docs/en/knowledgebase/runtime/frame">汇总链接</a></p>
<h3 id="Runtime宏">Runtime宏</h3>
<h4 id="decl-storage">decl_storage!</h4>
<p>在 pallet 中定义一个存储项目，存储项目的定义包括：</p>
<ul>
<li>数据类型，为下列其中一种：
<ul>
<li><code>StorageValue</code>类型: <code>rust-type</code></li>
<li><code>StorageMap</code>类型: <code>map hasher($hasher) rust_type =&gt; rust_type</code></li>
<li><code>StorageDoubleMap</code>类型: <code>doublemap hasher($hasher) rust_type, hasher($hasher) rust_type =&gt; rust_type</code></li>
</ul>
</li>
<li>getter函数</li>
<li>键类型及其哈希函数 (如果是map或double-map类型)</li>
<li>存储的名称</li>
<li>默认值</li>
</ul>
<p>这些存储值可通过其后的<code>add_extra_genesis</code>模块在其创世区块中进行初始化</p>
<figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">decl_storage! &#123;</span><br><span class="line">  <span class="class"><span class="keyword">trait</span> <span class="title">Storage</span></span> <span class="keyword">for</span> Module&lt;T: Config&gt; <span class="keyword">as</span> MyModule &#123;</span><br><span class="line">    <span class="comment">// ...</span></span><br><span class="line">  &#125;</span><br><span class="line">  add_extra_genesis &#123;</span><br><span class="line">    build (|config| &#123;</span><br><span class="line">      <span class="comment">//...</span></span><br><span class="line">    &#125;);</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p><a target="_blank" rel="noopener" href="https://substrate.dev/rustdocs/v3.0.0/frame_support/macro.decl_storage.html">API文档</a></p>
<h4 id="decl-event">decl_event!</h4>
<p>通过实现 <code>Event</code>枚举类型来定义pallet事件，而宏中的每个事件类型都是<code>Event</code>枚举类型内的一个成员</p>
<p><a target="_blank" rel="noopener" href="https://substrate.dev/rustdocs/v3.0.0/frame_support/macro.decl_event.html">API 文档</a></p>
<h4 id="decl-error">decl_error!</h4>
<p>定义 pallet 在可调用函数中可能返回的错误类型 <code>DispatchError</code> 。宏自动为<code>DispatchError</code> 实现了 <code>From&lt;Error&lt;T&gt;&gt;</code> trait， 因此，<code>DispatchError</code> 能为特定的错误类型返回正确的模块索引、错误代码、错误字符串</p>
<p><a target="_blank" rel="noopener" href="https://substrate.dev/rustdocs/v3.0.0/frame_support/macro.decl_error.html">API 文档</a></p>
<h4 id="decl-module">decl_module!</h4>
<p>定义pallet中的可调用函数，在此宏中，pallet声明了一个名为 <code>Module</code>的结构体，以及一个名为 <code>Call</code> 的枚举类型。除了为 <code>Module</code> 和 <code>Call</code>实现了各种辅助trait，如 <code>Copy</code>、<code>StructuralEq</code>、 <code>Debug</code>以外，该宏还为<code>Module</code>实现了生命周期trait，如 <code>frame_support::traits::OnInitialize</code>， <code>frame_support::traits::OnFinalize</code>， <code>frame_support::traits::OnRuntimeUpgrade</code>，和<code>frame_support::traits::OffchainWorker</code></p>
<p><a target="_blank" rel="noopener" href="https://substrate.dev/rustdocs/v3.0.0/frame_support/macro.decl_module.html">API 文档</a></p>
<h4 id="construct-runtime">construct_runtime!</h4>
<p>用于构造Substrate runtime，将各个pallets集成到runtime。该宏声明及实现了各种不同的结构体和枚举类型，如<code>Runtime</code>、<code>Event</code>、<code>Origin</code>、 <code>Call</code>、<code>GenesisConfig</code> 等，同时也为这些结构体类型实现了不同的辅助trait</p>
<p><a target="_blank" rel="noopener" href="https://substrate.dev/rustdocs/v3.0.0/frame_support/macro.construct_runtime.html">API 文档</a></p>
<ul>
<li><code>Runtime</code> 结构类型是为Substrate runtime而定义的</li>
<li><code>Event</code> 枚举类型的成员变量是所有可发出事件的pallets，并且实现了辅助trait和编码/解码trait。<code>Event</code> 实现了<code>TryInto&lt;pallets::Event&lt;Runtime&gt;&gt;</code> trait，以从枚举类型中提取事件</li>
<li><code>Origin</code>枚举类型是通过实现辅助traits来定义的，如 <code>PartialEq</code>、<code>Clone</code>、 <code>Debug</code>等trait。 此枚举类型定义了是谁调用了extrinsic：<code>NONE</code>、 <code>ROOT</code>还是由特定帐户签名调用</li>
<li><code>Call</code>枚举类型由所有的集成pallet作为成员变量来定义的。 它包含每个集成pallet的数据和元数据，并通过实现<code>frame_support::traits::UnfilteredDispatchable</code> trait将调用重定向到特定pallet</li>
<li>该宏定义了<code>GenesisConfig</code> 结构类型，并实现了<code>sp_runtime:: BuildStorage</code> trait以建立存储的创世配置</li>
<li>该宏收集每一个pallet对 <code>frame_support::unsigned::ValidateUnsigned</code> 这个trait的实现， 如果没有任何一个pallet实现了 <code>ValidateUnsigned</code> trait，则所有的无签名交易都将被拒绝</li>
</ul>
<h4 id="parameter-types">parameter_types!</h4>
<p>用于在构造runtime时声明参数类型，这些参数类型将赋值给各pallet的可配置trait关联类型。该宏使用<code>get()</code>函数返回的具体值，来替换掉结构体中指定的类型。 每个参数的结构体类型还实现了 <code>frame_support::traits::Get&lt;I&gt;</code> 这个trait，以将类型转换为其指定的值</p>
<p><a target="_blank" rel="noopener" href="https://substrate.dev/rustdocs/v3.0.0/frame_support/macro.parameter_types.html">API 文档</a></p>
<h4 id="impl-runtime-apis">impl_runtime_apis!</h4>
<p>通过<code>RuntimeApi</code>和<code>RuntimeApiImpl</code>这两个结构体类型为客户端实现API</p>
<p><a target="_blank" rel="noopener" href="https://substrate.dev/rustdocs/v3.0.0/sp_api/macro.impl_runtime_apis.html">API 文档</a></p>
<h4 id="add-crypto">add_crypto!</h4>
<p>指定交给pallet管理的密钥对及其签名算法。该宏声明了三种结构体类型: <code>Public</code>、<code>Signature</code>和<code>Pair</code></p>
<ul>
<li><code>Public</code> 类型用于生成密钥对、签名和验证签名</li>
<li><code>Signature</code> 类型用于在确定了签名加密方法情况下保存签名属性</li>
<li><code>Pair</code> 类型用于使用种子生成一个公私密钥对</li>
</ul>
<p><a target="_blank" rel="noopener" href="https://substrate.dev/rustdocs/v3.0.0/sp_application_crypto/macro.app_crypto.html">API 文档</a></p>
<h4 id="impl-outer-origin">impl_outer_origin!</h4>
<p>用于为runtime构造一个 <code>Origin</code>结构体类型，它通常由<code>construct_runtime!</code>自动调用</p>
<p><a target="_blank" rel="noopener" href="https://substrate.dev/rustdocs/v3.0.0/frame_support/macro.impl_outer_origin.html">API 文档</a></p>
<h4 id="impl-outer-event">impl_outer_event!</h4>
<p>用于在runtime时构造一个 <code>Event</code> 结构体类型， 它通常由 <code>construct_runtime!</code>宏自动调用</p>
<p><a target="_blank" rel="noopener" href="https://substrate.dev/rustdocs/v3.0.0/frame_support/macro.impl_outer_event.html">API 文档</a></p>
<h4 id="impl-outer-dispatch">impl_outer_dispatch!</h4>
<p>用于实现一个元调用模块，以把调用分派给其它调用者，它通常是由 <code>construct_runtime!</code>自动调用的</p>
<p><a target="_blank" rel="noopener" href="https://substrate.dev/rustdocs/v3.0.0/frame_support/macro.impl_outer_dispatch.html">API 文档</a></p>
<h3 id="Runtime元数据">Runtime元数据</h3>
<p>建立在 Substrate 上的区块链会暴露出元数据，以便能轻松与其交互。 元数据根据不同的 pallets来源被分隔成不同模块，对于每个模块，元数据都提供该模块对外暴露的 存储项、extrinsic 调用、事件、常量和错误的相关信息。 Substrate 会自动生成这些元数据，并通过 RPC 函数使它可被调用</p>
<p>可使用特定语言库或者与语言无关的HTTP和WebSocket API这两种渠道，来从Substrate节点中获取元数据</p>
<p><a target="_blank" rel="noopener" href="https://substrate.dev/docs/en/knowledgebase/runtime/metadata">具体参考</a></p>
<h3 id="Runtime执行流程">Runtime执行流程</h3>
<p>Substrate runtime的执行由Executive模块来协调，它负责调用区块链中包含的各种runtime模块</p>
<p>Executive模块对外暴露了 <code>execute_block</code> 函数，以实现如下功能：</p>
<ul>
<li>初始化区块</li>
<li>执行extrinsics</li>
<li>完结区块</li>
</ul>
<h4 id="验证交易">验证交易</h4>
<p>在区块开始执行前，检查签名交易的有效性</p>
<h4 id="执行区块">执行区块</h4>
<p>只要有效交易的队列不为空，Executive模块就开始执行区块</p>
<h5 id="初始化区块">初始化区块</h5>
<p>区块初始化时，System模块和其他runtime模块都会首先调用其<code>on_initialize</code> 函数，把由模块定义的、需要前置的业务逻辑在交易执行前全部处理掉。 除System模块总是优先处理外，其余模块均按照在<code>construct_runtime!</code>宏里定义的顺序来执行</p>
<p>接下来是初始检查，该步骤将验证区块头中的父哈希是否正确，以及extrinsics trie的根是否囊括了所有的extrinsics</p>
<h5 id="执行Extrinsics">执行Extrinsics</h5>
<p>按照交易优先级顺序执行每一个有效的extrinsic。 Extrinsics一定不能在rutnime逻辑中引起程序崩溃，否则系统将很容易受到用户攻击，而通过这种攻击，用户可不受任何惩罚地消耗计算资源</p>
<p>当extrinsic执行时，原有存储状态不会提前被缓存下来，修改将直接应用到存储上。 因此，在更改存储状态之前，runtime开发人员应进行所有必要检查，以确保extrinsic能执行成功。 一旦extrinsic在执行过程中失败了，存储更改将不能回滚</p>
<p>extrinsic执行时触发的事件也会写入存储。 因此，在完成所有待执行动作之前，不应该触发相关事件。 否则，倘若extrinsic在事件触发后才执行失败的话，该事件将不能回滚</p>
<h5 id="完结区块">完结区块</h5>
<p>执行完所有队列中的extrinsic之后，Executive模块调用各模块的 <code>on_idle</code> 和 <code>on_finalize</code> 函数来执行区块的最后业务逻辑</p>
<h2 id="智能合约">智能合约</h2>
<h3 id="概述">概述</h3>
<h4 id="智能合约与Runtime开发的关系">智能合约与Runtime开发的关系</h4>
<p>Substrate Runtime 开发和 Substrate 智能合约是使用 Substrate 框架来构建 “去中心化应用” 的两种不同途径</p>
<h5 id="智能合约-v2">智能合约</h5>
<p>传统的智能合约平台允许用户在核心区块链逻辑之上发布额外的逻辑，为保证安全性，智能合约平台内建了一些安全防护手段，包括：</p>
<ul>
<li>Fees：确保合约开发者在使用了区块链的计算和存储资源来执行智能合约之后付费，这样出块节点的资源就不会被他们滥用</li>
<li>沙箱：一个合约无法直接修改核心区块链存储或其他合约的存储</li>
<li>状态租赁：合约会因为占用了区块链的空间而需要为其付费</li>
<li>回滚：合约可能有导致逻辑错误的情况，我们对合约开发者开发能力的期望很低，因此增加了额外的开销，以支持在交易失败时回滚整个交易</li>
</ul>
<h5 id="Runtime开发">Runtime开发</h5>
<p>Runtime 开发不向开发者提供智能合约所提供的那些保护或安全措施，相反，可以完全控制网络上每个节点运行的基本逻辑，也拥有修改和控制所有模块的每一个存储条目的完整权限</p>
<p>Substrate Runtime 开发的目的是为区块链提供精炼、高性能、和快速的节点。 它不提供任何保护，不提供交易回退的开销， 也不隐式地引入区块链上节点运行的收费系统</p>
<h5 id="两者对比">两者对比</h5>
<table>
<thead>
<tr>
<th>智能合约</th>
<th>Runtime</th>
</tr>
</thead>
<tbody>
<tr>
<td>对网络来说是天生安全</td>
<td>提供对整个区块链的底层访问权限</td>
</tr>
<tr>
<td>通过经济激励来防止滥用</td>
<td>没有任何原生的经济激励机制来抵御作恶</td>
</tr>
<tr>
<td>通过额外的计算开销来支持错误处理</td>
<td>没有内置安全措施带来的性能开销</td>
</tr>
<tr>
<td>开发门槛更低</td>
<td>开发者需要逾越一定的门槛</td>
</tr>
</tbody>
</table>
<h3 id="ink-智能合约">ink!智能合约</h3>
<p>Substrate使用ink!作为其智能合约的语言，ink!是一个基于 Rust 的嵌入式领域专用语言（eDSL），专用于编写Contracts模块的 Wasm 智能合约，其设计宗旨是正确性、简洁性、高效性</p>
<p>ink! 设计上尽可能接近 Rust 编程语言，使用属性宏将标准的 Rust 结构标记为可理解的合约组件</p>
<figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#[ink(...)]</span></span><br></pre></td></tr></table></figure>
<p>ink!的合约开发组件包括：</p>
<ul>
<li>事件 (Events)</li>
<li>事件主题 (Event Topics)</li>
<li>存储 (Storage)</li>
<li>构造函数 (Constructor Functions)</li>
<li>消息函数 (Message Functions)</li>
</ul>
<p>因为是采用 Rust 编写，ink! 可以提供编译时的溢出/下溢安全保护</p>
<h3 id="ink-语言基础">ink!语言基础</h3>
<h3 id="合约-Contracts-模块">合约(Contracts)模块</h3>
<p>合约模块为 Runtime 提供部署和执行wasm智能合约的能力</p>
<h4 id="Wasm引擎">Wasm引擎</h4>
<p>合约模块依赖于 Wasm 的沙盒接口，它定义了 Runtime 内可用的 Wasm 执行引擎（<code>wasmi</code>）</p>
<h4 id="功能">功能</h4>
<p>合约模块在智能合约的部署和执行上有许多合约开发者熟悉的功能以及一些新功能</p>
<h5 id="合约账户">合约账户</h5>
<p>对 Substrate Runtime 来说，合约账户就像普通的用户账户一样；但是，除了普通账户所拥有的 <code>AccountID</code> 和 <code>Balance</code> 之外，合约账户还有相关的合约代码和一些持久的合约存储</p>
<h5 id="部署合约">部署合约</h5>
<p>用 Contracts 模块部署合约需要两个步骤：</p>
<ol>
<li>在区块链上存储 Wasm 合约</li>
<li>开启一个由新存储空间的新账户，与该智能合约挂勾</li>
</ol>
<p>这意味着可以使用同一个 Wasm 代码初始化多个具有不同的构造参数的合约实例，从而减少区块链上 Contracts 模块所需的存储空间</p>
<h5 id="合约调用">合约调用</h5>
<p>调用合约可以改变合约内的储存、创建新合约，或调用其他合约。 由于可以编写自定义 Runtime 模块，Contracts 模块也可以用合约账户直接异步调用那些 Runtime 函数</p>
<h5 id="沙箱保护">沙箱保护</h5>
<p>Contracts 模块旨在供公共网络上的所有用户使， 这意味着合约只能直接修改他们自己所拥有的存储。 为了给底层区块链状态提供安全保障，合约模块实现了可逆交易，可回滚那些对存储进行改动而没有成功完成的合约调用</p>
<h5 id="手续费">手续费</h5>
<p>为了限制一次交易可使用的计算资源，合约调用需要收取手续费 (gas fee)，在构造合约交易时，需要指定 gas 限额。随着合约的执行，gas 根据计算的复杂性逐步被消耗。如果在合约执行完成前达到 gas 限额，则交易失败，合约存储被还原，gas 费用 <strong>并不会</strong> 退还给用户；如果合约执行完成时还有剩余 gas，则在交易结束时退还给用户</p>
<h5 id="存储租金">存储租金</h5>
<p>与 gas 限制了交易的计算资源类似，存储租金限制了合约在区块链存储中的占用率，合约账户按其使用的存储量所占的比例支付租金。 当合约可用余额低于某个限额时，合约账户会变成一个 “墓碑”，其存储被清空。 墓碑合约可通过提供能激活它的最少资金和被清除的数据，来重新激活合约</p>

  
  
    
    <div class='footer'>
      
        <div class='references'>
          <section class='header'>
            
              <i class="fas fa-quote-left fa-fw" aria-hidden="true"></i>
            
            <span>参考资料</span>
          </section>
          <section class='body'>
            <ul>
              
                <li>
                  <a href="https://substrate.dev/?utm_source=subdevcn&utm_medium=post&utm_campaign=weekly_post" rel="external nofollow noopener noreferrer" target="_blank">
                  Substrate Developer Hub
                  </a>
                </li>
              
            </ul>
          </section>
        </div>
      
      
      
      
    </div>
  
  
    


  <div class='article-meta' id="bottom">
    <div class='new-meta-box'>
      
        
          <div class="new-meta-item date">
  <a class='notlink'>
    <i class="fas fa-calendar-alt fa-fw" aria-hidden="true"></i>
    <p>发布于：2021年4月15日</p>
  </a>
</div>

        
      
        
          
  
  <div class="new-meta-item meta-tags"><a class="tag" href="/tags/blockchain/" rel="nofollow"><i class="fas fa-hashtag fa-fw" aria-hidden="true"></i><p>blockchain</p></a></div>


        
      
    </div>
  </div>


  
  

  
    <div class="prev-next">
      
        <a class='prev' href='/2021/06/06/paper-reading/paper-hawk/'>
          <p class='title'><i class="fas fa-chevron-left" aria-hidden="true"></i>论文阅读-Hawk.The Blockchain Model of Cryptography and Privacy-Preserving Smart Contracts</p>
          <p class='content'>1.引入
区块链上的可信时钟的存在对于协议中实现公平交易至关重要：恶意用户可能会过早地中止协议以避免资金支付，但是有了可信时钟，过早地中止协议会被判定为超时，这样区块链就可以将恶意用户的抵押存款...</p>
        </a>
      
      
        <a class='next' href='/2021/04/12/paper-reading/paper-twine/'>
          <p class='title'>论文阅读-TWINE An Embedded Trusted Runtime<i class="fas fa-chevron-right" aria-hidden="true"></i></p>
          <p class='content'>1.Introduction
TWINE（Trusted Wasm in Enclave）

使用标准Intel工具链实现
允许本地执行遗留Wasm应用程序，而无需重新编译
动态地将WASI操作...</p>
        </a>
      
    </div>
  
</article>


  

  






</div>
<aside class='l_side'>
  
  
    
    



  <section class="widget toc-wrapper shadow floatable desktop mobile" id="toc-div" >
    
  <header>
    
      <i class="fas fa-list fa-fw" aria-hidden="true"></i><span class='name'>本文目录</span>
    
  </header>


    <div class='content'>
        <ol class="toc"><li class="toc-item toc-level-2"><a class="toc-link" href="#Start"><span class="toc-text">Start</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#%E6%9E%B6%E6%9E%84"><span class="toc-text">架构</span></a></li></ol></li><li class="toc-item toc-level-2"><a class="toc-link" href="#%E5%9F%BA%E6%9C%AC%E6%A6%82%E5%BF%B5"><span class="toc-text">基本概念</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#Extrinsics"><span class="toc-text">Extrinsics</span></a><ol class="toc-child"><li class="toc-item toc-level-4"><a class="toc-link" href="#%E5%8C%BA%E5%9D%97%E7%BB%93%E6%9E%84"><span class="toc-text">区块结构</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#Inherents"><span class="toc-text">Inherents</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#%E7%AD%BE%E5%90%8D%E4%BA%A4%E6%98%93"><span class="toc-text">签名交易</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#%E6%97%A0%E7%AD%BE%E5%90%8D%E4%BA%A4%E6%98%93"><span class="toc-text">无签名交易</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#Signed-Extension"><span class="toc-text">Signed Extension</span></a></li></ol></li><li class="toc-item toc-level-3"><a class="toc-link" href="#%E4%BA%A4%E6%98%93%E6%B1%A0"><span class="toc-text">交易池</span></a><ol class="toc-child"><li class="toc-item toc-level-4"><a class="toc-link" href="#%E4%BA%A4%E6%98%93%E4%BE%9D%E8%B5%96%E5%85%B3%E7%B3%BB"><span class="toc-text">交易依赖关系</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#%E4%BA%A4%E6%98%93%E4%BC%98%E5%85%88%E7%BA%A7"><span class="toc-text">交易优先级</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#%E4%BA%A4%E6%98%93%E7%9A%84%E6%B5%81%E7%A8%8B"><span class="toc-text">交易的流程</span></a><ol class="toc-child"><li class="toc-item toc-level-5"><a class="toc-link" href="#%E6%88%91%E4%BB%AC%E7%9A%84%E8%8A%82%E7%82%B9%E7%94%9F%E6%88%90%E7%9A%84%E5%8C%BA%E5%9D%97"><span class="toc-text">我们的节点生成的区块</span></a></li><li class="toc-item toc-level-5"><a class="toc-link" href="#%E4%BB%8E%E7%BD%91%E7%BB%9C%E6%8E%A5%E6%94%B6%E7%9A%84%E5%8C%BA%E5%9D%97"><span class="toc-text">从网络接收的区块</span></a></li></ol></li><li class="toc-item toc-level-4"><a class="toc-link" href="#%E4%BA%A4%E6%98%93%E6%9C%89%E6%95%88%E6%80%A7"><span class="toc-text">交易有效性</span></a></li></ol></li><li class="toc-item toc-level-3"><a class="toc-link" href="#%E8%B4%A6%E6%88%B7"><span class="toc-text">账户</span></a><ol class="toc-child"><li class="toc-item toc-level-4"><a class="toc-link" href="#%E8%B4%A6%E6%88%B7%E5%AF%86%E9%92%A5"><span class="toc-text">账户密钥</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#Stash%E5%AF%86%E9%92%A5"><span class="toc-text">Stash密钥</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#Controller%E5%AF%86%E9%92%A5"><span class="toc-text">Controller密钥</span></a></li></ol></li><li class="toc-item toc-level-3"><a class="toc-link" href="#%E4%BC%9A%E8%AF%9D%E5%AF%86%E9%92%A5"><span class="toc-text">会话密钥</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#%E4%BA%A4%E6%98%93%E6%9D%83%E9%87%8D"><span class="toc-text">交易权重</span></a><ol class="toc-child"><li class="toc-item toc-level-4"><a class="toc-link" href="#%E6%9D%83%E9%87%8D%E5%9F%BA%E7%A1%80"><span class="toc-text">权重基础</span></a></li></ol></li><li class="toc-item toc-level-3"><a class="toc-link" href="#%E9%93%BE%E4%B8%8B%E5%8A%9F%E8%83%BD"><span class="toc-text">链下功能</span></a></li></ol></li><li class="toc-item toc-level-2"><a class="toc-link" href="#Runtime"><span class="toc-text">Runtime</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#Runtime%E5%9F%BA%E6%9C%AC%E7%B1%BB%E5%9E%8B"><span class="toc-text">Runtime基本类型</span></a><ol class="toc-child"><li class="toc-item toc-level-4"><a class="toc-link" href="#%E6%A0%B8%E5%BF%83%E5%8E%9F%E8%AF%AD"><span class="toc-text">核心原语</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#FRAME%E5%8E%9F%E8%AF%AD"><span class="toc-text">FRAME原语</span></a></li></ol></li><li class="toc-item toc-level-3"><a class="toc-link" href="#FRAME"><span class="toc-text">FRAME</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#Pallet"><span class="toc-text">Pallet</span></a><ol class="toc-child"><li class="toc-item toc-level-4"><a class="toc-link" href="#Pallet%E6%9E%B6%E6%9E%84"><span class="toc-text">Pallet架构</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#Substrate%E5%86%85%E7%BD%AE%E6%A8%A1%E5%9D%97"><span class="toc-text">Substrate内置模块</span></a></li></ol></li><li class="toc-item toc-level-3"><a class="toc-link" href="#Runtime%E5%AE%8F"><span class="toc-text">Runtime宏</span></a><ol class="toc-child"><li class="toc-item toc-level-4"><a class="toc-link" href="#decl-storage"><span class="toc-text">decl_storage!</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#decl-event"><span class="toc-text">decl_event!</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#decl-error"><span class="toc-text">decl_error!</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#decl-module"><span class="toc-text">decl_module!</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#construct-runtime"><span class="toc-text">construct_runtime!</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#parameter-types"><span class="toc-text">parameter_types!</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#impl-runtime-apis"><span class="toc-text">impl_runtime_apis!</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#add-crypto"><span class="toc-text">add_crypto!</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#impl-outer-origin"><span class="toc-text">impl_outer_origin!</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#impl-outer-event"><span class="toc-text">impl_outer_event!</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#impl-outer-dispatch"><span class="toc-text">impl_outer_dispatch!</span></a></li></ol></li><li class="toc-item toc-level-3"><a class="toc-link" href="#Runtime%E5%85%83%E6%95%B0%E6%8D%AE"><span class="toc-text">Runtime元数据</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#Runtime%E6%89%A7%E8%A1%8C%E6%B5%81%E7%A8%8B"><span class="toc-text">Runtime执行流程</span></a><ol class="toc-child"><li class="toc-item toc-level-4"><a class="toc-link" href="#%E9%AA%8C%E8%AF%81%E4%BA%A4%E6%98%93"><span class="toc-text">验证交易</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#%E6%89%A7%E8%A1%8C%E5%8C%BA%E5%9D%97"><span class="toc-text">执行区块</span></a><ol class="toc-child"><li class="toc-item toc-level-5"><a class="toc-link" href="#%E5%88%9D%E5%A7%8B%E5%8C%96%E5%8C%BA%E5%9D%97"><span class="toc-text">初始化区块</span></a></li><li class="toc-item toc-level-5"><a class="toc-link" href="#%E6%89%A7%E8%A1%8CExtrinsics"><span class="toc-text">执行Extrinsics</span></a></li><li class="toc-item toc-level-5"><a class="toc-link" href="#%E5%AE%8C%E7%BB%93%E5%8C%BA%E5%9D%97"><span class="toc-text">完结区块</span></a></li></ol></li></ol></li></ol></li><li class="toc-item toc-level-2"><a class="toc-link" href="#%E6%99%BA%E8%83%BD%E5%90%88%E7%BA%A6"><span class="toc-text">智能合约</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#%E6%A6%82%E8%BF%B0"><span class="toc-text">概述</span></a><ol class="toc-child"><li class="toc-item toc-level-4"><a class="toc-link" href="#%E6%99%BA%E8%83%BD%E5%90%88%E7%BA%A6%E4%B8%8ERuntime%E5%BC%80%E5%8F%91%E7%9A%84%E5%85%B3%E7%B3%BB"><span class="toc-text">智能合约与Runtime开发的关系</span></a><ol class="toc-child"><li class="toc-item toc-level-5"><a class="toc-link" href="#%E6%99%BA%E8%83%BD%E5%90%88%E7%BA%A6-v2"><span class="toc-text">智能合约</span></a></li><li class="toc-item toc-level-5"><a class="toc-link" href="#Runtime%E5%BC%80%E5%8F%91"><span class="toc-text">Runtime开发</span></a></li><li class="toc-item toc-level-5"><a class="toc-link" href="#%E4%B8%A4%E8%80%85%E5%AF%B9%E6%AF%94"><span class="toc-text">两者对比</span></a></li></ol></li></ol></li><li class="toc-item toc-level-3"><a class="toc-link" href="#ink-%E6%99%BA%E8%83%BD%E5%90%88%E7%BA%A6"><span class="toc-text">ink!智能合约</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#ink-%E8%AF%AD%E8%A8%80%E5%9F%BA%E7%A1%80"><span class="toc-text">ink!语言基础</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#%E5%90%88%E7%BA%A6-Contracts-%E6%A8%A1%E5%9D%97"><span class="toc-text">合约(Contracts)模块</span></a><ol class="toc-child"><li class="toc-item toc-level-4"><a class="toc-link" href="#Wasm%E5%BC%95%E6%93%8E"><span class="toc-text">Wasm引擎</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#%E5%8A%9F%E8%83%BD"><span class="toc-text">功能</span></a><ol class="toc-child"><li class="toc-item toc-level-5"><a class="toc-link" href="#%E5%90%88%E7%BA%A6%E8%B4%A6%E6%88%B7"><span class="toc-text">合约账户</span></a></li><li class="toc-item toc-level-5"><a class="toc-link" href="#%E9%83%A8%E7%BD%B2%E5%90%88%E7%BA%A6"><span class="toc-text">部署合约</span></a></li><li class="toc-item toc-level-5"><a class="toc-link" href="#%E5%90%88%E7%BA%A6%E8%B0%83%E7%94%A8"><span class="toc-text">合约调用</span></a></li><li class="toc-item toc-level-5"><a class="toc-link" href="#%E6%B2%99%E7%AE%B1%E4%BF%9D%E6%8A%A4"><span class="toc-text">沙箱保护</span></a></li><li class="toc-item toc-level-5"><a class="toc-link" href="#%E6%89%8B%E7%BB%AD%E8%B4%B9"><span class="toc-text">手续费</span></a></li><li class="toc-item toc-level-5"><a class="toc-link" href="#%E5%AD%98%E5%82%A8%E7%A7%9F%E9%87%91"><span class="toc-text">存储租金</span></a></li></ol></li></ol></li></ol></li></ol>
    </div>
  </section>


  


</aside>



        
        
          <!--此文件用来存放一些不方便取值的变量-->
<!--思路大概是将值藏到重加载的区域内-->

<script>
  window.pdata={}
  pdata.ispage=true;
  pdata.postTitle="Substrate入门";
  pdata.commentPath="";
  pdata.commentPlaceholder="";

  var l_header=document.getElementById("l_header");
  
  l_header.classList.add("show");
  
</script>

        
      </div>
      
  
  <footer class="footer clearfix">
    <br><br>
    
      
        <div class='copyright'>
        <p><a target="_blank" rel="noopener" href="https://github.com/Schenk75/Schenk75.github.io">Copyright © 2020-2022 Schenk</a></p>

        </div>
      
    
  </footer>


      <a id="s-top" class="fas fa-arrow-up fa-fw" href='javascript:void(0)'></a>
    </div>
  </div>
  <div>
    <script>
window.volantis={};
window.volantis.loadcss=document.getElementById("loadcss");
/********************脚本懒加载函数********************************/
function loadScript(src, cb) {
var HEAD = document.getElementsByTagName('head')[0] || document.documentElement;
var script = document.createElement('script');
script.setAttribute('type','text/javascript');
if (cb) script.onload = cb;
script.setAttribute('src', src);
HEAD.appendChild(script);
}
//https://github.com/filamentgroup/loadCSS
var loadCSS = function( href, before, media, attributes ){
	var doc = window.document;
	var ss = doc.createElement( "link" );
	var ref;
	if( before ){
		ref = before;
	}
	else {
		var refs = ( doc.body || doc.getElementsByTagName( "head" )[ 0 ] ).childNodes;
		ref = refs[ refs.length - 1];
	}
	var sheets = doc.styleSheets;
	if( attributes ){
		for( var attributeName in attributes ){
			if( attributes.hasOwnProperty( attributeName ) ){
				ss.setAttribute( attributeName, attributes[attributeName] );
			}
		}
	}
	ss.rel = "stylesheet";
	ss.href = href;
	ss.media = "only x";
	function ready( cb ){
		if( doc.body ){
			return cb();
		}
		setTimeout(function(){
			ready( cb );
		});
	}
	ready( function(){
		ref.parentNode.insertBefore( ss, ( before ? ref : ref.nextSibling ) );
	});
	var onloadcssdefined = function( cb ){
		var resolvedHref = ss.href;
		var i = sheets.length;
		while( i-- ){
			if( sheets[ i ].href === resolvedHref ){
				return cb();
			}
		}
		setTimeout(function() {
			onloadcssdefined( cb );
		});
	};
	function loadCB(){
		if( ss.addEventListener ){
			ss.removeEventListener( "load", loadCB );
		}
		ss.media = media || "all";
	}
	if( ss.addEventListener ){
		ss.addEventListener( "load", loadCB);
	}
	ss.onloadcssdefined = onloadcssdefined;
	onloadcssdefined( loadCB );
	return ss;
};
</script>
<script>
  
  loadCSS("https://cdn.jsdelivr.net/npm/@fortawesome/fontawesome-free@5.14/css/all.min.css", window.volantis.loadcss);
  
  
  
  
</script>
<!-- required -->

<script src="https://cdn.jsdelivr.net/npm/jquery@3.5/dist/jquery.min.js"></script>

<script>
  function pjax_fancybox() {
    $(".md .gallery").find("img").each(function () { //渲染 fancybox
      var element = document.createElement("a"); // a 标签
      $(element).attr("class", "fancybox");
      $(element).attr("pjax-fancybox", "");  // 过滤 pjax
      $(element).attr("href", $(this).attr("src"));
      if ($(this).attr("data-original")) {
        $(element).attr("href", $(this).attr("data-original"));
      }
      $(element).attr("data-fancybox", "images");
      var caption = "";   // 描述信息
      if ($(this).attr('alt')) {  // 判断当前页面是否存在描述信息
        $(element).attr('data-caption', $(this).attr('alt'));
        caption = $(this).attr('alt');
      }
      var div = document.createElement("div");
      $(div).addClass("fancybox");
      $(this).wrap(div); // 最外层套 div ，其实主要作用还是 class 样式
      var span = document.createElement("span");
      $(span).addClass("image-caption");
      $(span).text(caption); // 加描述
      $(this).after(span);  // 再套一层描述
      $(this).wrap(element);  // 最后套 a 标签
    })
    $(".md .gallery").find("img").fancybox({
      selector: '[data-fancybox="images"]',
      hash: false,
      loop: false,
      closeClick: true,
      helpers: {
        overlay: {closeClick: true}
      },
      buttons: [
        "zoom",
        "close"
      ]
    });
  };
  function SCload_fancybox() {
    if ($(".md .gallery").find("img").length == 0) return;
    loadCSS("https://cdn.jsdelivr.net/npm/@fancyapps/fancybox@3.5.7/dist/jquery.fancybox.min.css", document.getElementById("loadcss"));
    setTimeout(function() {
      loadScript('https://cdn.jsdelivr.net/gh/fancyapps/fancybox@3.5.7/dist/jquery.fancybox.min.js', pjax_fancybox)
    }, 1);
  };
  $(function () {
    SCload_fancybox();
  });
</script>


<!-- internal -->







  <script defer src="https://cdn.jsdelivr.net/npm/vanilla-lazyload@17.1.0/dist/lazyload.min.js"></script>
<script>
  // https://www.npmjs.com/package/vanilla-lazyload
  // Set the options globally
  // to make LazyLoad self-initialize
  window.lazyLoadOptions = {
    elements_selector: ".lazyload",
    threshold: 0
  };
  // Listen to the initialization event
  // and get the instance of LazyLoad
  window.addEventListener(
    "LazyLoad::Initialized",
    function (event) {
      window.lazyLoadInstance = event.detail.instance;
    },
    false
  );
  document.addEventListener('DOMContentLoaded', function () {
    lazyLoadInstance.update();
  });
  document.addEventListener('pjax:complete', function () {
    lazyLoadInstance.update();
  });
</script>




  
  
    <script>
      window.FPConfig = {
        delay: 0,
        ignoreKeywords: [],
        maxRPS: 5,
        hoverDelay: 25
      };
    </script>
    <script defer src="https://cdn.jsdelivr.net/gh/gijo-varghese/flying-pages@2.1.2/flying-pages.min.js"></script>
  




  <script src="https://cdn.jsdelivr.net/npm/clipboard@2/dist/clipboard.min.js"></script>
<script>
    var clipboard = new ClipboardJS('.btn-copy', {
        target: function (trigger) {
            return trigger.nextElementSibling
        }
    });
    function wait(callback, seconds) {
        var timelag = null;
        timelag = window.setTimeout(callback, seconds)
    }
    function pjax_initCopyCode() {
		if($(".highlight .code pre").length+$(".article pre code").length==0)return;
        var copyHtml = '';
        copyHtml += '<button class="btn-copy" data-clipboard-snippet="">';
        copyHtml += '<i class="fas fa-copy"></i><span>COPY</span>';
        copyHtml += '</button>';
        $(".highlight .code pre").before(copyHtml);
        $(".article pre code").before(copyHtml);
        clipboard.off('success').on('success', function (e) {
            let $btn = $(e.trigger);
            $btn.addClass('copied');
            let $icon = $($btn.find('i'));
            $icon.removeClass('fa-copy');
            $icon.addClass('fa-check-circle');
            let $span = $($btn.find('span'));
            $span[0].innerText = 'COPIED';
            wait(function () {
                $icon.removeClass('fa-check-circle');
                $icon.addClass('fa-copy');
                $span[0].innerText = 'COPY'
            }, 2000)
        });
        clipboard.off('error').on('error', function (e) {
            e.clearSelection();
            let $btn = $(e.trigger);
            $btn.addClass('copy-failed');
            let $icon = $($btn.find('i'));
            $icon.removeClass('fa-copy');
            $icon.addClass('fa-times-circle');
            let $span = $($btn.find('span'));
            $span[0].innerText = 'COPY FAILED';
            wait(function () {
                $icon.removeClass('fa-times-circle');
                $icon.addClass('fa-copy');
                $span[0].innerText = 'COPY'
            }, 2000)
        })
    }
    $(function () {
        pjax_initCopyCode()
    });
</script>










  <script defer src="https://cdn.jsdelivr.net/gh/Schenk75/Source@master/tools/busuanzi.pure.mini.js" data-pjax></script>


  
<script src="https://cdn.jsdelivr.net/npm/hexo-theme-volantis@4.1.5/source/js/app.min.js"></script>




  
  
<script src="https://cdn.jsdelivr.net/npm/hexo-theme-volantis@4.1.5/source/js/search.min.js"></script>

  


<!-- optional -->

  <script>
const SearchServiceimagePath="https://cdn.jsdelivr.net/gh/volantis-x/cdn-volantis@master/img/";
const ROOT =  ("/" || "/").endsWith('/') ? ("/" || "/") : ("//" || "/" );
function listenSearch(){
  
    customSearch = new HexoSearch({
      imagePath: SearchServiceimagePath
    });
  
}
document.addEventListener("DOMContentLoaded", listenSearch);

</script>











  <script defer>

  const LCCounter = {
    app_id: 'u9j57bwJod4EDmXWdxrwuqQT-MdYXbMMI',
    app_key: 'jfHtEKVE24j0IVCGHbvuFClp',
    custom_api_server: '',

    // 查询存储的记录
    getRecord(Counter, url, title) {
      return new Promise(function (resolve, reject) {
        Counter('get', '/classes/Counter?where=' + encodeURIComponent(JSON.stringify({url})))
          .then(resp => resp.json())
          .then(({results, code, error}) => {
            if (code === 401) {
              throw error;
            }
            if (results && results.length > 0) {
              var record = results[0];
              resolve(record);
            } else {
              Counter('post', '/classes/Counter', {url, title: title, times: 0})
                .then(resp => resp.json())
                .then((record, error) => {
                  if (error) {
                    throw error;
                  }
                  resolve(record);
                }).catch(error => {
                console.error('Failed to create', error);
                reject(error);
              });
            }
          }).catch((error) => {
          console.error('LeanCloud Counter Error:', error);
          reject(error);
        });
      })
    },

    // 发起自增请求
    increment(Counter, incrArr) {
      return new Promise(function (resolve, reject) {
        Counter('post', '/batch', {
          "requests": incrArr
        }).then((res) => {
          res = res.json();
          if (res.error) {
            throw res.error;
          }
          resolve(res);
        }).catch((error) => {
          console.error('Failed to save visitor count', error);
          reject(error);
        });
      });
    },

    // 构建自增请求体
    buildIncrement(objectId) {
      return {
        "method": "PUT",
        "path": `/1.1/classes/Counter/${ objectId }`,
        "body": {
          "times": {
            '__op': 'Increment',
            'amount': 1
          }
        }
      }
    },

    // 校验是否为有效的 UV
    validUV() {
      var key = 'LeanCloudUVTimestamp';
      var flag = localStorage.getItem(key);
      if (flag) {
        // 距离标记小于 24 小时则不计为 UV
        if (new Date().getTime() - parseInt(flag) <= 86400000) {
          return false;
        }
      }
      localStorage.setItem(key, new Date().getTime().toString());
      return true;
    },

    addCount(Counter) {
      var enableIncr = '' === 'true' && window.location.hostname !== 'localhost';
      enableIncr = true;
      var getterArr = [];
      var incrArr = [];
      // 请求 PV 并自增
      var pvCtn = document.querySelector('#lc-sv');
      if (pvCtn || enableIncr) {
        var pvGetter = this.getRecord(Counter, 'http://example.com' + '/#lc-sv', 'Visits').then((record) => {
          incrArr.push(this.buildIncrement(record.objectId))
          var eles = document.querySelectorAll('#lc-sv #number');
          if (eles.length > 0) {
            eles.forEach((el,index,array)=>{
              el.innerText = record.times + 1;
              if (pvCtn) {
                pvCtn.style.display = 'inline';
              }
            })
          }
        });
        getterArr.push(pvGetter);
      }

      // 请求 UV 并自增
      var uvCtn = document.querySelector('#lc-uv');
      if (uvCtn || enableIncr) {
        var uvGetter = this.getRecord(Counter, 'http://example.com' + '/#lc-uv', 'Visitors').then((record) => {
          var vuv = this.validUV();
          vuv && incrArr.push(this.buildIncrement(record.objectId))
          var eles = document.querySelectorAll('#lc-uv #number');
          if (eles.length > 0) {
            eles.forEach((el,index,array)=>{
              el.innerText = record.times + (vuv ? 1 : 0);
              if (uvCtn) {
                uvCtn.style.display = 'inline';
              }
            })
          }
        });
        getterArr.push(uvGetter);
      }

      // 请求文章的浏览数，如果是当前页面就自增
      var allPV = document.querySelectorAll('#lc-pv');
      if (allPV.length > 0 || enableIncr) {
        for (i = 0; i < allPV.length; i++) {
          let pv = allPV[i];
          let title = pv.getAttribute('data-title');
          var url = 'http://example.com' + pv.getAttribute('data-path');
          if (url) {
            var viewGetter = this.getRecord(Counter, url, title).then((record) => {
              // 是当前页面就自增
              let curPath = window.location.pathname;
              if (curPath.includes('index.html')) {
                curPath = curPath.substring(0, curPath.lastIndexOf('index.html'));
              }
              if (pv.getAttribute('data-path') == curPath) {
                incrArr.push(this.buildIncrement(record.objectId));
              }
              if (pv) {
                var ele = pv.querySelector('#lc-pv #number');
                if (ele) {
                  if (pv.getAttribute('data-path') == curPath) {
                    ele.innerText = (record.times || 0) + 1;
                  } else {
                    ele.innerText = record.times || 0;
                  }
                  pv.style.display = 'inline';
                }
              }
            });
            getterArr.push(viewGetter);
          }
        }
      }

      // 如果启动计数自增，批量发起自增请求
      if (enableIncr) {
        Promise.all(getterArr).then(() => {
          incrArr.length > 0 && this.increment(Counter, incrArr);
        })
      }

    },


    fetchData(api_server) {
      var Counter = (method, url, data) => {
        return fetch(`${ api_server }/1.1${ url }`, {
          method,
          headers: {
            'X-LC-Id': this.app_id,
            'X-LC-Key': this.app_key,
            'Content-Type': 'application/json',
          },
          body: JSON.stringify(data)
        });
      };
      this.addCount(Counter);
    },

    refreshCounter() {
      var api_server = this.app_id.slice(-9) !== '-MdYXbMMI' ? this.custom_api_server : `https://${ this.app_id.slice(0, 8).toLowerCase() }.api.lncldglobal.com`;
      if (api_server) {
        this.fetchData(api_server);
      } else {
        fetch('https://app-router.leancloud.cn/2/route?appId=' + this.app_id)
          .then(resp => resp.json())
          .then(({api_server}) => {
            this.fetchData('https://' + api_server);
          });
      }
    }

  };

  LCCounter.refreshCounter();

  document.addEventListener('pjax:complete', function () {
    LCCounter.refreshCounter();
  });
</script>








<script>
function listennSidebarTOC() {
  const navItems = document.querySelectorAll(".toc li");
  if (!navItems.length) return;
  const sections = [...navItems].map((element) => {
    const link = element.querySelector(".toc-link");
    const target = document.getElementById(
      decodeURI(link.getAttribute("href")).replace("#", "")
    );
    link.addEventListener("click", (event) => {
      event.preventDefault();
      window.scrollTo({
		top: target.offsetTop + 100,
		
		behavior: "smooth"
		
	  });
    });
    return target;
  });

  function activateNavByIndex(target) {
    if (target.classList.contains("active-current")) return;

    document.querySelectorAll(".toc .active").forEach((element) => {
      element.classList.remove("active", "active-current");
    });
    target.classList.add("active", "active-current");
    let parent = target.parentNode;
    while (!parent.matches(".toc")) {
      if (parent.matches("li")) parent.classList.add("active");
      parent = parent.parentNode;
    }
  }

  function findIndex(entries) {
    let index = 0;
    let entry = entries[index];
    if (entry.boundingClientRect.top > 0) {
      index = sections.indexOf(entry.target);
      return index === 0 ? 0 : index - 1;
    }
    for (; index < entries.length; index++) {
      if (entries[index].boundingClientRect.top <= 0) {
        entry = entries[index];
      } else {
        return sections.indexOf(entry.target);
      }
    }
    return sections.indexOf(entry.target);
  }

  function createIntersectionObserver(marginTop) {
    marginTop = Math.floor(marginTop + 10000);
    let intersectionObserver = new IntersectionObserver(
      (entries, observe) => {
        let scrollHeight = document.documentElement.scrollHeight + 100;
        if (scrollHeight > marginTop) {
          observe.disconnect();
          createIntersectionObserver(scrollHeight);
          return;
        }
        let index = findIndex(entries);
        activateNavByIndex(navItems[index]);
      },
      {
        rootMargin: marginTop + "px 0px -100% 0px",
        threshold: 0,
      }
    );
    sections.forEach((element) => {
      element && intersectionObserver.observe(element);
    });
  }
  createIntersectionObserver(document.documentElement.scrollHeight);
}

document.addEventListener("DOMContentLoaded", listennSidebarTOC);
document.addEventListener("pjax:success", listennSidebarTOC);
</script>

<!-- more -->




    
      


<script src="https://cdn.jsdelivr.net/npm/pjax@0.2.8/pjax.min.js"></script>


<script>
    var pjax;
    document.addEventListener('DOMContentLoaded', function () {
      pjax = new Pjax({
        elements: 'a[href]:not([href^="#"]):not([href="javascript:void(0)"]):not([pjax-fancybox])',
        selectors: [
          "title",
          "#l_cover",
          "#pjax-container",
          "#pjax-header-nav-list"
        ],
        cacheBust: false,   // url 地址追加时间戳，用以避免浏览器缓存
        timeout: 5000
      });
    });

    document.addEventListener('pjax:send', function (e) {
      //window.stop(); // 相当于点击了浏览器的停止按钮

      try {
        var currentUrl = window.location.pathname;
        var targetUrl = e.triggerElement.href;
        var banUrl = [""];
        if (banUrl[0] != "") {
          banUrl.forEach(item => {
            if(currentUrl.indexOf(item) != -1 || targetUrl.indexOf(item) != -1) {
              window.location.href = targetUrl;
            }
          });
        }
      } catch (error) {}

      window.subData = null; // 移除标题（用于一二级导航栏切换处）
      if (typeof $.fancybox != "undefined") {
        $.fancybox.close();    // 关闭弹窗
      }
      volantis.$switcher.removeClass('active'); // 关闭移动端激活的搜索框
      volantis.$header.removeClass('z_search-open'); // 关闭移动端激活的搜索框
      volantis.$wrapper.removeClass('sub'); // 跳转页面时关闭二级导航

      // 解绑事件 避免重复监听
      volantis.$topBtn.unbind('click');
      $('.menu a').unbind('click');
      $(window).unbind('resize');
      $(window).unbind('scroll');
      $(document).unbind('scroll');
      $(document).unbind('click');
      $('body').unbind('click');
	  
    });

    document.addEventListener('pjax:complete', function () {
      // 关于百度统计对 SPA 页面的处理：
      // 方案一：百度统计>管理>单页应用设置中，打开开启按钮即可对SPA进行统计。 https://tongji.baidu.com/web/help/article?id=324
      // 方案二：取消注释下列代码。 https://tongji.baidu.com/web/help/article?id=235
      // 

      // 关于谷歌统计对 SPA 页面的处理：
      // 当应用以动态方式加载内容并更新地址栏中的网址时，也应该更新通过 gtag.js 存储的网页网址。
      // https://developers.google.cn/analytics/devguides/collection/gtagjs/single-page-applications?hl=zh-cn
      

      $('.nav-main').find('.list-v').not('.menu-phone').removeAttr("style",""); // 移除小尾巴的移除
      $('.menu-phone.list-v').removeAttr("style",""); // 移除小尾巴的移除
      $('script[data-pjax], .pjax-reload script').each(function () {
        $(this).parent().append($(this).remove());
      });
      try{
          if (typeof $.fancybox == "undefined") {
            SCload_fancybox();
          } else {
            pjax_fancybox();
          }
        
        
        
        
        
          pjax_initCopyCode();
        
        
        
        
        
      } catch (e) {
        console.log(e);
      }
	  
    });

    document.addEventListener('pjax:error', function (e) {
	  
      window.location.href = e.triggerElement.href;
    });
</script>

    
  </div>
</body>
</html>
